# _*_ coding: utf-8 _*_
#!/usr/bin/env python

#      ___________________
#  ___| Modulbeschreibung \_________________________________________________________________________
# | Ist für die grafische Darstellung während eines Rennens zuständig (nicht im Menü).
# | Es werden folgende Elemente auf den Bildschirm gerendert:
# |   > Bildschirmtexte, Rennzeit, Rundennummer, Spieleranzahl ect.
# |   > Minimap mit Spielern darauf zeichnen
# |   > Darstellung des Mode7-Effekts, also 3D rendern der Gamemap
# |   > Darstellung der Spielersprites auf dem Bildschirm
# |
# |__________________________________________________________________| Zuständigkeit [Hendrik] |____

import sys, os
import pygame
import math
sys.path.append(os.path.join(os.path.dirname(sys.argv[0]), 'lib/NumPtr/build/lib.linux-i686-2.5'))
import NumPtr
sys.path.append(os.path.join(os.path.dirname(sys.argv[0]), 'lib'))
import mode7

def addWorkPath(filename):
	"""Aus Portabilitätsgruenden wie relativen Pfadangaben unter verschiedenen Systemen gehandhabt werden. Haengt das Arbeitsverzeichnis vor den übergebenen Dateinamen"""
	return os.path.join(os.path.dirname(sys.argv[0]), 'data', filename)

def rotate_point((x, y), angle):
	"""Rotiert den übergebenen Punkt um den angegebenen Winkel (Bogenmaß)"""
	matrix = [	math.cos(angle), -math.sin(angle),
				math.sin(angle), math.cos(angle)]
	return (	round(x * matrix[0] + y * matrix[1]),
				round(x * matrix[2] + y * matrix[3]))

class Graphic(object):
	def __init__(self, race):
		"""Konstruktor"""
		self.race		= race
		self.game_map	= race.game_map
		self.sprites	= pygame.image.load(addWorkPath('Mario_Sprites.png')).convert(32)
		self.screen		= race.game.screen
		self.screen_size	= (self.screen_width,
		self.screen_height)	= (self.race.game.settings['screen_size'][0] - 10, self.race.game.settings['screen_size'][1] -10)
		self.cam		= {	'angle':	0.0,
							'xpos':		124,
							'ypos':		124,
							'zpos':		1000,
							'xscale':	0.003,
							'yscale':	0.2,
							'horizon':	0,
							}
		
		# Wichtig, damit es sich um normale 32Bit-Integer handelt. 32Bit Tiefe ist zwar ueblich, aber
		#  nicht garantiert. Auf Systemen mit andren Vorgaben fuehrt die Umwandlung zu Zeitverlusten
		self.mode7_surf	= pygame.Surface(self.screen_size).convert(32)
		
		# Ressourcen - Fonts
		self.fonts		= {	'tiny':					pygame.font.Font(addWorkPath('Visitor_TT2_BRK.ttf'), 12),
							'mario_small_solid':	pygame.font.Font(addWorkPath('SuperMarioBros_filled.ttf'), 18),
							'mario_small_border':	pygame.font.Font(addWorkPath('SuperMarioBros.ttf'), 18),
							'mario_normal_solid':	pygame.font.Font(addWorkPath('SuperMarioBros_filled.ttf'), 26),
							'mario_normal_border':	pygame.font.Font(addWorkPath('SuperMarioBros.ttf'), 26),
							'mario_huge_solid':		pygame.font.Font(addWorkPath('SuperMarioBros_filled.ttf'), 60),
							'mario_huge_border':	pygame.font.Font(addWorkPath('SuperMarioBros.ttf'), 60)
							}
	
	def showRaceIntro(self):
		pass
	
	def drawScene(self):
		"""Aktualisiert die 3D-Darstellung und die Schriften auf dem Bildschirm"""
		num					= self.race.network.getPlayernumber()
		self.cam['angle']	= self.race.players[num].angle
		self.cam['xpos']	= (self.race.players[num].x_pos - math.cos(self.cam['angle']) * 25) + 8
		self.cam['ypos']	= (self.race.players[num].y_pos - math.sin(self.cam['angle']) * 25) + 8
		
		# Bildschirm loeschen
		self.screen.fill((0, 0, 0))
		
		# Erzeugt ein Numeric-Array mit den Pixeln des Bildschirms und einen Pointer auf dieses
		output_array		= pygame.surfarray.pixels2d(self.mode7_surf)
		screenpointer		= NumPtr.getpointer(output_array)
		# Aufuf der C-Funktion im Mode7 Modul
		mode7.draw_mode7(	screenpointer,						# Pointer auf die Zielflaeche
							self.screen_width,					# X-Groesse der Zielflaeche
							self.screen_height,					# Y-Groesse der Zielflaeche
							
							self.game_map.tilemap_pointer,		# Pointer auf die Tilemap
							self.game_map.tilemap_size[0],		# X-Groesse der Tilemap (in Tiles)
							self.game_map.tilemap_size[1],		# Y-Groesse der Tilemap (in Tiles)
							
							self.game_map.raw_tileset_pointer,	# Pointer auf das Tileset
							self.game_map.tileset_size[0],		# X-Groesse des Tilesets (in Tiles)
							self.game_map.tileset_size[1],		# Y-Groesse des Tilesets (in Tiles)
							self.game_map.tile_size[0],			# X-Groesse der Tiles
							self.game_map.tile_size[1],			# Y-Groesse der Tiles
							0x3D,								# Randtile
							
							self.cam['angle'],					# Kamerawinkel
							self.cam['xpos'],					# X-Position der Kamera
							self.cam['ypos'],					# Y-Position der Kamera
							self.cam['zpos'],					# Kamerahoehe ueber der Flaeche
							self.cam['horizon'],				# Pixel die der Horizont unter der Nullten Zeile ist
							self.cam['xscale'],					# X-Skalierung
							self.cam['yscale'],					# Y-Skalierung
							1,									# Frame fuer animiert Tiles
							1,									# Nebeldistanz (0 fuer daktiviert)
							0xA0A0A0)							# Nebelfarbe RGB
		# Das Array muss gelöscht werden, damit die Surface wieder entsperrt wird
		del output_array
		
		# -- Sprites zeichnen --
		x_scale		= 350
		y_scale		= 5
		sprite_size	= 32
		obj_scale	= 1.5
		for kart in self.race.players:
			if self.race.network.getPlayerInf()[kart.number] != None:
				# Objektkoordinaten relativ zur Kamera berechnen
				obj_x		= kart.x_pos - self.cam['xpos'] + 8
				obj_y		= kart.y_pos - self.cam['ypos'] + 8
				#if obj_y > 0:
					#continue
				
				# Position des Spielers im Raum (Kamera als Nullpunkt) bestimmen
				angle		= self.cam['angle']
				space_x		= obj_x * math.cos(angle) + obj_y * math.sin(angle)
				space_y		= -(obj_x * math.sin(angle)) + obj_y * math.cos(angle)
				
				# Bildschirmkoordiaten des Spielers ermittel
				screen_x	= int((x_scale / space_x) * space_y) + self.screen_width / 2
				screen_y	= int((self.cam['zpos'] * y_scale) / space_x - self.cam['horizon'])
				
				# Skalierung des Sprites ermitteln
				scale		= (sprite_size * (obj_scale / space_x)) * 1.7
				scaled_sprite_size		= int(sprite_size * scale)
				half_scaled_sprite_size	= scaled_sprite_size >> 1
				# Befindet sich das Sprite ueberhaupt im Sichtbereich?
				if -half_scaled_sprite_size < screen_x < self.screen_width + half_scaled_sprite_size and -scaled_sprite_size < screen_y and scaled_sprite_size > 3 and scaled_sprite_size < 256:
					# Mario oder Luigi?
					if kart.number != self.race.network.getPlayernumber():
						sprite_nr	= 1
					else:
						sprite_nr	= 0
					# Winkel zur Kamera
					angle = self.cam['angle'] - kart.angle - 0.3
					# Winkel soll nicht zu groß oder klein werden!
					if angle < 0:
						angle = math.pi * 2 + angle
					elif angle > math.pi * 2:
						angle = angle - math.pi * 2
					sprite_frame		= int((math.pi * 2 - (angle)) / (math.pi / 10))
					# Wenn Winkel größer als Halbkreis (ein Pi), dann Sprite spiegeln
					if sprite_frame > 11:
						sprite_frame	= 22 - sprite_frame
						# Spritenummer ermittel und Spritesurface erzeugen
						sprite_yoffset	= sprite_frame / 6 + sprite_nr * 3
						sprite_xoffset	= sprite_frame % 6
						sprite = pygame.transform.flip(self.sprites.subsurface((sprite_xoffset * 32, sprite_yoffset * 32, 32, 32)), True, False)
					else:
						sprite_yoffset	= sprite_frame / 6 + sprite_nr * 3
						sprite_xoffset	= sprite_frame % 6
						sprite = self.sprites.subsurface((sprite_xoffset * 32, sprite_yoffset * 32, 32, 32)).copy()
					# Sprite auf den Bildschirm kleben
					self.mode7_surf.blit(pygame.transform.scale(sprite, (scaled_sprite_size, scaled_sprite_size)), (screen_x - scaled_sprite_size / 2, screen_y - scaled_sprite_size * 0.7))
					
					#pygame.draw.circle(self.mode7_surf, (200, 0, 0), (int(screen_x), int(screen_y)), 2)

		# -- Minimap zeichnen --
		self.mode7_surf.blit(self.game_map.minimap, (5, self.screen_height - self.game_map.tilemap_size[1] - 5))
		
		# DEBUG - Checkpoints einzeichnen
#		y_offset	= self.screen_height - self.game_map.tilemap_size[1] - 5
#		for checkpoint in self.game_map.checkpoints:
#			kart	= self.race.players[0]
#			if (checkpoint[0][0] - 2 < kart.x_pos / self.game_map.tile_size[0] < checkpoint[1][0] + 2
#				and checkpoint[0][1] - 2 < kart.y_pos / self.game_map.tile_size[0] < checkpoint[1][1] + 2):
#				color = (255, 0, 0)
#			else:
#				color = (0, 0, 0)
#			pygame.draw.rect(self.mode7_surf, color, (checkpoint[0][0] + 5, y_offset + checkpoint[0][1],
#				checkpoint[1][0] - checkpoint[0][0], checkpoint[1][1] - checkpoint[0][1]), 1)
#			pygame.draw.line(self.mode7_surf, (255, 255, 0), (checkpoint[0][0] + 5, y_offset + checkpoint[0][1]),
#				(checkpoint[1][0] + 5, y_offset + checkpoint[1][1]), 2)
		
		# Karts auf die Minimap zeichen
		src_polygon			= ((0, 7), (7, -7), (0, -3), (-7, -7))
		for kart in self.race.players:
			if self.race.network.getPlayerInf()[kart.number] != None:
				des_polygon		= []
				angle			= kart.angle + math.pi * 1.5
				center			= (kart.x_pos / self.game_map.tile_size[0] + 5,
									kart.y_pos / self.game_map.tile_size[1] + self.screen_height - self.game_map.tilemap_size[1] - 5)
				# Pfeil des Spieler entsprechend dem Kartwinkel rotieren
				for point in src_polygon:
					new_point	= rotate_point(point, angle)
					des_polygon.append((center[0] + new_point[0],
										center[1] + new_point[1]))
				# Ist man selber der Pfeil, anders einfärben
				if kart == self.race.players[0]:																					#//!!!
					color = (255, 255, 0)
				else:
					color = (255, 0, 0)
				pygame.draw.polygon(self.mode7_surf, color, des_polygon, 2)
				# Spielernummer in den Pfeil schreiben
				text		= str(kart.number+1)
				text_size	= self.fonts['tiny'].size(text)
				self.mode7_surf.blit(self.fonts['tiny'].render(text, False, (0, 0, 0)),
					(center[0] - text_size[0] / 2, center[1] - text_size[1] / 2))
		
		self.screen.blit(self.mode7_surf, (5, 5))
		
		# -- Texte auf den Bildschirm zeichnen --
		# Seit dem Rennstart vergangene Zeit berechnen
		time = (pygame.time.get_ticks() - self.race.starttime) / 10
		t_min = t_sec = t_mil_sec = 0
		if time >= 6000:
			t_min		= time / 6000
			time %= 6000
		if time >= 100:
			t_sec		= time / 100
			time %= 100
		if time > 0:
			t_mil_sec	= time
		self.screen.blit(self.fonts['mario_normal_solid'].render('%.2i' % t_min, True, (255, 10, 10)), (370, 10))
		self.screen.blit(self.fonts['mario_normal_solid'].render('\'%.2i' % t_sec, True, (255, 10, 10)), (410, 10))
		self.screen.blit(self.fonts['mario_normal_solid'].render('\'\'%.2i' % t_mil_sec, True, (255, 10, 10)), (450, 10))
		self.screen.blit(self.fonts['mario_normal_border'].render('%.2i' % t_min, True, (0, 0, 0)), (370, 10))
		self.screen.blit(self.fonts['mario_normal_border'].render('\'%.2i' % t_sec, True, (0, 0, 0)), (410, 10))
		self.screen.blit(self.fonts['mario_normal_border'].render('\'\'%.2i' % t_mil_sec, True, (0, 0, 0)), (450, 10))
		
		self.screen.blit(self.fonts['mario_small_solid'].render('Runde', True, (255, 10, 10)), (370, 45))
		self.screen.blit(self.fonts['mario_small_border'].render('Runde', True, (0, 0, 0)), (370, 45))
		
		self.screen.blit(self.fonts['mario_normal_solid'].render('%i/3' % self.race.game_logic.rounds, True, (255, 10, 10)), (440, 45))
		self.screen.blit(self.fonts['mario_normal_border'].render('%i/3' % self.race.game_logic.rounds, True, (0, 0, 0)), (440, 45))
		
		self.screen.blit(self.fonts['mario_huge_solid'].render(str(self.race.network.getPlayernumber()+1), True, (255, 10, 10)), (420, 230))
		self.screen.blit(self.fonts['mario_normal_solid'].render('/', True, (255, 10, 10)), (460, 260))
		self.screen.blit(self.fonts['mario_normal_solid'].render('%i' % len(self.race.players), True, (255, 10, 10)), (480, 270))
		self.screen.blit(self.fonts['mario_huge_border'].render(str(self.race.network.getPlayernumber()+1), True, (0, 0, 0)), (420, 230))
		self.screen.blit(self.fonts['mario_normal_border'].render('/', True, (0, 0, 0)), (460, 260))
		self.screen.blit(self.fonts['mario_normal_border'].render('%i' % len(self.race.players), True, (0, 0, 0)), (480, 270))
		#self.screen.blit(self.fonts['mario_huge_border'].render('%i/3' % self.race.game_logic.rounds, True, (0, 0, 0)), (440, 45))

		# DEBUG - Debugtexte auf den Bildschirm zeichnen
		self.screen.blit(self.fonts['tiny'].render('FPS: %i' % self.race.game.clock.get_fps(), False, (255, 10, 10), (0, 0, 0)), (5, 2))
#		#self.screen.blit(self.tiny_font.render('%i' % pygame.time.get_ticks(), False, (255, 10, 10), (0, 0, 0)), (5, 25))
#		self.screen.blit(self.fonts['tiny'].render('x-pos: %i cam: %i' % (self.race.players[0].x_pos, self.cam['xpos']), False, (255, 10, 10), (0, 0, 0)), (5, 15))
#	self.screen.blit(self.fonts['tiny'].render('y-pos: %i cam: %i' % (self.race.players[0].y_pos, self.cam['ypos']), False, (255, 10, 10), (0, 0, 0)), (5, 24))
#		self.screen.blit(self.fonts['tiny'].render('deg %.1f' % math.degrees(self.cam['angle']), False, (255, 10, 10), (0, 0, 0)),(5, 33))
#		self.screen.blit(self.fonts['tiny'].render('Ang: rad %.2f' % self.cam['angle'], False, (255, 10, 10), (0, 0, 0)), (5, 42))
		#self.screen.blit(self.fonts['tiny'].render('Next-CP %i von %i' % (self.race.game_logic.next_checkpoint, len(self.game_map.checkpoints)), False, (255, 10, 10)), (400, 70))

#		text_xpos = 400
#		tile_pos	= int(self.race.players[0].x_pos / self.game_map.tile_size[0]), int(self.race.players[0].y_pos / self.game_map.tile_size[1])
#		tile		= self.game_map.tilemap[tile_pos[1]][tile_pos[0]]
#		tilecluster	= (self.game_map.tileset_size[0] / 2) & tile
#		self.screen.blit(self.fonts['tiny'].render('TilePos.: %.2i/%.2i' % (tile_pos[0], tile_pos[1]), False, (0, 0, 0)), (text_xpos, 10))
#		self.screen.blit(self.fonts['tiny'].render('TileNr.: %.2i' % tile, False, (0, 0, 0)), (text_xpos, 19))
#		self.screen.blit(self.fonts['tiny'].render('Tilecluster.: %.2i' % tilecluster, False, (0, 0, 0)), (text_xpos, 28))
		
		# Screen upfdaten
		pygame.display.flip()
